package com.mrzhou.mybatis.plus;

import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.mrzhou.core.tool.utils.StringUtil;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Statement;

/**
 * mybatils sql 日志;
 *  Signature注解: 拦截点, 只有符合拦截点条件才会进入拦截器
 *      属性说明:
 *          type: 需要拦截的对象; Executor、ParameterHandler、StatementHandler、ResultSetHandler
 *          method: 在定义的拦截类上, 继续拦截指定的方法
 *          args: 在定义拦截方法的基础上, 指定拦截方法的参数; java可以重载,不指定参数无法知道拦截哪一个方法
 */
@Intercepts({
        @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
        @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
        @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SqlLogInterceptor implements Interceptor {
    private static final Logger log = LoggerFactory.getLogger(SqlLogInterceptor.class);
    private final String DRUID_POOL_STATEMENT = "com.alibaba.druid.pool.DruidPooledPreparedStatement";
    private Method druidGetSqlMethod;

    /**
     *  在sql执行前拦截的方法。在这个方法里面做我们自定义的逻辑处理
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object obj = invocation.getArgs()[0];
        Statement statement;
        if(Proxy.isProxyClass(obj.getClass())) { // 判断对象是否是一个代理对象
            statement = (Statement) SystemMetaObject.forObject(obj).getValue("h.statement");
        } else {
            statement = (Statement) obj;
        }
        MetaObject stmtMetaObject = SystemMetaObject.forObject(statement);

        try{
            statement = (Statement) stmtMetaObject.getValue("stmt.statement");
        } catch (Exception var19) {
        }

        if (stmtMetaObject.hasGetter("delegate")) {
            try {
                statement = (Statement) stmtMetaObject.getValue("delegate");
            } catch (Exception var18) {
            }
        }

        String originSql = null;
        String stmtClassName = statement.getClass().getName();
        Class clazz;
        Object stmtSql = null;
        if(DRUID_POOL_STATEMENT.equals(stmtClassName)) {
            try{
                if(this.druidGetSqlMethod != null) {
//                    clazz =Class.forName(DRUID_POOL_STATEMENT);
                    stmtSql = this.druidGetSqlMethod.invoke(statement);
                }
                if (stmtSql instanceof String) {
                    originSql = (String) stmtSql;
                }
            } catch (Exception var17) {
                var17.printStackTrace();
            }
        }

        if (originSql == null) {
            originSql = statement.toString();
        }

        long start = SystemClock.now();
        Object result = invocation.proceed();
        long timing = SystemClock.now() - start;
        Object target = PluginUtils.realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(target);
        MappedStatement ms = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
        log.info("\n=============== Sql Start ===============\n Execute ID: {}\n Execute SQL: {}\n Execute Time：{}ms\n=============== Sql End ===============\n", ms.getId(), StringUtil.formatSql(originSql), timing);
        return result;
    }
}
